﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityServer.ClaimsPolicy.Engine.AttributeStore;
using System.IdentityModel;
using System.Net;
using System.IO;
using Newtonsoft.Json;

namespace CNRS.gaas
{
	/// <summary>
	/// CNRS Grouper ADFS Attribute Store (CNRSgaas) allows retrievial of a user groups in Grouper through the VOOT connector
	/// CNRSgaas is distributed under CeCILL 2.1 license, fully compatible with the GNU GPL license (http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html)
	///
	/// (C) CNRS - Jean Marie THIA - April 2015
	/// 
	/// </summary>
	/// 
	/// <!--
	/// reference : http://blogs.technet.com/b/cloudpfe/archive/2013/12/27/how-to-create-a-custom-attribute-store-for-active-directory-federation-services-3-0.aspx
	/// -->
	public class CNRSgaas : IAttributeStore
	{
		// configuration vars
		string _vootUrl;
		string _userName;
		string _password;

		
		/// <summary>
		/// 
		/// </summary>
		/// <param name="query">unused</param>
		/// <param name="parameters">the grouper id of the user</param>
		/// <param name="callback"></param>
		/// <param name="state"></param>
		/// <returns></returns>
		public IAsyncResult BeginExecuteQuery(string query, string[] parameters, AsyncCallback callback, object state)
		{

			// attributes in the query are separated with ,
			Char[] attributeSplitChar = new Char[] { ',' };

			if (parameters == null)
			{
				throw new AttributeStoreQueryFormatException("No query parameter.");
			}

			if (parameters.Length != 1)
			{
				throw new AttributeStoreQueryFormatException("More than one query parameter.");
			}

			// Get the userName through the unique parameter
			string inputString = parameters[0];

			if (inputString == null)
			{
				throw new AttributeStoreQueryFormatException("Query parameter cannot be null.");
			}

			// Call Grouper with Voot protocol
			List<Entry> vootRes = GetDataElements(inputString);

			// Transform the voot call result to attribute store values
			string[][] outputValues = null;

			if (vootRes != null && vootRes.Count > 0)
			{
				outputValues = new string[vootRes.Count][];
				int i = 0;

				foreach (Entry entry in vootRes)
				{
					outputValues[i] = new string[] { entry.id };
					i++;
				}
			}


			// Return the output array
			TypedAsyncResult<string[][]> asyncResult = new TypedAsyncResult<string[][]>(callback, state);
			asyncResult.Complete(outputValues, true);
			return asyncResult;
		}

		public string[][] EndExecuteQuery(IAsyncResult result)
		{
			return TypedAsyncResult<string[][]>.End(result);
		}

		public void Initialize(Dictionary<string, string> config)
		{
			if (config == null)
			{
				throw new ArgumentNullException("config");
			}

			if (!config.TryGetValue("vooturl", out _vootUrl))
			{
				throw new AttributeStoreInvalidConfigurationException("vooturl configuration entry not found");
			}

			if (string.IsNullOrEmpty(_vootUrl))
			{
				throw new AttributeStoreInvalidConfigurationException("vooturl should be valid");
			}


			if (!config.TryGetValue("username", out _userName))
			{
				throw new AttributeStoreInvalidConfigurationException("username configuration entry not found");
			}

			if (string.IsNullOrEmpty(_userName))
			{
				throw new AttributeStoreInvalidConfigurationException("username should be valid");
			}

			if (!config.TryGetValue("password", out _password))
			{
				throw new AttributeStoreInvalidConfigurationException("password configuration entry not found");
			}

			if (string.IsNullOrEmpty(_password))
			{
				throw new AttributeStoreInvalidConfigurationException("password should be valid");
			}
		}

		/// <summary>
		/// Search in a particular node RefList to found a match
		/// </summary>
		/// <param name="searchPattern"></param>
		/// <param name="node"></param>
		public List<Entry> GetDataElements(string searchPattern)
		{

			string json = string.Empty;
			List<Entry> result = null;

			if (!string.IsNullOrEmpty(searchPattern))
			{

				string searchUrl = _vootUrl + "/groups/" + searchPattern;

				string autorization = _userName + ":" + _password;
				byte[] binaryAuthorization = System.Text.Encoding.UTF8.GetBytes(autorization);
				autorization = Convert.ToBase64String(binaryAuthorization);
				autorization = "Basic " + autorization;

				try
				{
					HttpWebRequest request = (HttpWebRequest)WebRequest.Create(searchUrl);
					request.Method = "GET";
					//request.ContentType = "application/json";
					request.Headers.Add("AUTHORIZATION", autorization);

					// call grouper voot web service
					HttpWebResponse response = (HttpWebResponse)request.GetResponse();
					if (response.StatusCode == HttpStatusCode.OK)
					{
						// store the response in a string
						using (Stream responseStream = response.GetResponseStream())
						{
							StreamReader stream = new StreamReader(responseStream, Encoding.UTF8);
							json = stream.ReadToEnd();
							stream.Close();
						}

						// transform the json string to .Net object
						var res = JsonConvert.DeserializeObject<GrouperEntry>(json);
						result = res.entry;

					}
					// TODO : log when response is not OK
				}
				catch (WebException ex)
				{
					WebResponse errorResponse = ex.Response;
					using (Stream responseStream = errorResponse.GetResponseStream())
					{
						StreamReader reader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8"));
						String errorText = reader.ReadToEnd();
						//LogHelper.LogError(ex, errorText);
						
					}
				}
			}
			return result;
		}

	}

	#region Json to Entity mapping class
	/*
	 * Object class genertaed by Json2csharp used by json.net to transform 
	 * Grouper Json answer into POCO
	 */
	public class Entry
	{
		public string description { get; set; }
		public string id { get; set; }
		public string name { get; set; }
	}

	public class GrouperEntry
	{
		public List<Entry> entry { get; set; }
		public int itemsPerPage { get; set; }
		public int startIndex { get; set; }
		public int totalResults { get; set; }
	}
	#endregion Json to Entity mapping class


}
